package org.turbobytes.unikey.helper.impl;

/**
 * Implementation class that exposes methods to retrieve SQL statements. This implementation uses XPath to
 * parse the XML and retrieves SQL statements.
 *
 * @author Gopalakrishnan T
 * @since Aug 25, 2010
 */

import java.io.IOException;
import java.io.InputStream;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.turbobytes.unikey.helper.SqlRetriever;
import org.turbobytes.unikey.util.FileUtil;
import org.turbobytes.unikey.util.db.metadata.DbType;
import org.turbobytes.unikey.util.exception.SqlRetrieverException;
import org.turbobytes.unikey.util.exception.UnsupportedDbException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XPathSqlRetriever implements SqlRetriever {

  private static final String XML_CONFIG_FILE = "db-config.xml";

  private static Document xmlDocument;

  private static XPath xPath;

  /**
   * Static block to load and store the XML in a static variable.
   */
  static {
    try {
      InputStream is = FileUtil.getFileStreamFromClasspath(XML_CONFIG_FILE);
      xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
      xPath = XPathFactory.newInstance().newXPath();
    } catch (IOException ex) {
      throw new SqlRetrieverException(ex);
    } catch (SAXException ex) {
      throw new SqlRetrieverException(ex);
    } catch (ParserConfigurationException ex) {
      throw new SqlRetrieverException(ex);
    }
  }

  /**
   * To read the XML and find out the Node depending upon the expression given.
   * 
   * @param expression
   *          Expression which to be evaluated.
   * @param returnType
   *          Return Type
   * @return Object - Node Kist
   */
  private Object read(String expression, QName returnType) {
    try {
      XPathExpression xPathExpression = xPath.compile(expression);
      return xPathExpression.evaluate(xmlDocument, returnType);
    } catch (XPathExpressionException ex) {
      throw new SqlRetrieverException(ex);
    }
  }

  /**
   * To retrive the SQL script for selecting records from the table.
   * 
   * @param dbType -
   *          Represents the current DB in use
   * 
   * @return SQL script for selecting records in to the table.
   */
  public String getSelectScript(DbType dbType) {
    NodeList rootNode = (NodeList) read("/unikey/select/db", XPathConstants.NODESET);
    String nodeValue = getNodeValue(rootNode, dbType.getName());

    if (nodeValue == null) {
      throw new UnsupportedDbException(dbType.getName() + " is not supported.");
    }
    return nodeValue;
  }

  /**
   * To retrive the SQL script for Creating table.
   * 
   * @param dbType -
   *          Represents the current DB in use
   * 
   * @return SQL script for creating table.
   */
  public String getCreateScript(DbType dbType) {
    NodeList rootNode = (NodeList) read("/unikey/create/db", XPathConstants.NODESET);
    String nodeValue = getNodeValue(rootNode, dbType.getName());

    if (nodeValue == null) {
      throw new UnsupportedDbException(dbType.getName() + " is not supported.");
    }
    return nodeValue;
  }

  /**
   * To retrive the SQL script for updating records in the table.
   * 
   * @param dbType -
   *          Represents the current DB in use
   * 
   * @return SQL script for updating records in to the table.
   */
  public String getInsertScript(DbType dbType) {
    NodeList rootNode = (NodeList) read("/unikey/insert/db", XPathConstants.NODESET);
    String nodeValue = getNodeValue(rootNode, dbType.getName());

    if (nodeValue == null) {
      throw new UnsupportedDbException(dbType.getName() + " is not supported.");
    }

    return nodeValue;
  }

  /**
   * To retrive the SQL script for inserting records in to the table.
   * 
   * @param dbType -
   *          Represents the current DB in use
   * 
   * @return SQL script for inserting records in to the table.
   */
  public String getUpdateScript(DbType dbType) {
    NodeList rootNode = (NodeList) read("/unikey/update/db", XPathConstants.NODESET);
    String nodeValue = getNodeValue(rootNode, dbType.getName());

    if (nodeValue == null) {
      throw new UnsupportedDbException(dbType.getName() + " is not supported.");
    }
    return nodeValue;
  }

  /**
   * To get the SQL script depending upon the nodename passed.
   * 
   * @param rootNode
   *          Node List
   * @param nodeName
   *          Node Name
   * @return SQL Script
   */
  private String getNodeValue(NodeList rootNode, String nodeName) {
    String nodeValue = null;
    for (int index = 0; index < rootNode.getLength(); index++) {
      Node aNode = rootNode.item(index);

      if (aNode.getNodeType() == Node.ELEMENT_NODE) {
        if (nodeName.equalsIgnoreCase(aNode.getAttributes().getNamedItem("name").getNodeValue())) {
          NodeList childNodes = aNode.getChildNodes();
          if (childNodes.getLength() > 0) {
            nodeValue = childNodes.item(1).getTextContent();
          }
        }
      }
    }
    return nodeValue;
  }
}
